package com.marketlive.app.b2c.struts;

import com.sourcebeat.strutslive.common.PojoForm;
import com.sourcebeat.strutslive.util.StringUtils;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Locale;
import java.util.Stack;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.beanutils.MethodUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.config.ControllerConfig;
import org.apache.struts.config.ForwardConfig;
import org.apache.struts.config.ModuleConfig;
import org.apache.struts.tiles.TilesRequestProcessor;
import org.apache.struts.upload.MultipartRequestHandler;
import org.apache.struts.util.MessageResources;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.struts.DelegatingActionUtils;

public class PojoSpringTilesRequestsProcessor extends TilesRequestProcessor
{
  private static Log log = LogFactory.getLog(PojoSpringTilesRequestsProcessor.class);
  private static final String ROLLBACK_MULTIPART_MSG = "  Rolling back multipart request";
  private static final String NO_INPUT_FORM_MSG = "  Validation failed but no input form available";
  private static final String RETURNING_TO_MSG = " Validation failed, returning to '";
  private static final String PREFIX_CHECKOUT = "checkout";
  private static final String PREFIX_ACCOUNT = "account";
  private static final String PREFIX_WISHLIST = "wishlist";
  private static final String CHECKOUT_LOGIN_FORWARD_PATH = "/checkout.do?method=start";
  private static final String ACCOUNT_LOGIN_FORWARD_PATH = "/account.do?method=start";
  private static final String LOGIN_FORWARD_PATH = "/login.do";
  private WebApplicationContext webApplicationContext;

  public void init(ActionServlet actionServlet, ModuleConfig moduleConfig)
    throws ServletException
  {
    super.init(actionServlet, moduleConfig);
    if (actionServlet != null)
      this.webApplicationContext = initWebApplicationContext(actionServlet, moduleConfig);
  }

  protected WebApplicationContext initWebApplicationContext(ActionServlet actionServlet, ModuleConfig moduleConfig)
    throws IllegalStateException
  {
    return DelegatingActionUtils.getRequiredWebApplicationContext(actionServlet, moduleConfig);
  }

  protected final WebApplicationContext getWebApplicationContext()
  {
    return this.webApplicationContext;
  }

  protected Action getDelegateAction(ActionMapping mapping)
    throws BeansException
  {
    String beanName = determineActionBeanName(mapping);
    return (Action)this.webApplicationContext.getBean(beanName, Action.class);
  }

  protected String determineActionBeanName(ActionMapping mapping)
  {
    return DelegatingActionUtils.determineActionBeanName(mapping);
  }

  public void process(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
  {
    request = processMultipart(request);

    String path = processPath(request, response);

    if (path == null) return;
    if (log.isDebugEnabled()) {
      log.debug("Processing a '" + request.getMethod() + "' for path '" + path + "'");
    }

    processContent(request, response);
    processNoCache(request, response);

    if (!processPreprocess(request, response)) return;

    //invokeMethod(this, "processCachedMessages", new Object[] { request, response });

    ActionMapping mapping = processMapping(request, response, path);
    if (mapping == null) return;

    Action action = processActionCreate(request, response, mapping);

    ActionForm form = processActionForm(request, response, mapping);
    if (!processForm(request, response, form, mapping, action)) return;

    if (!processRoles(request, response, mapping)) return;

    if (!processForward(request, response, mapping)) return;
    if (!processInclude(request, response, mapping)) return;

    if (action == null) return;

    ActionForward forward = processActionPerform(request, response, action, form, mapping);

    processForwardConfig(request, response, forward);
  }

  protected void processForwardConfig(HttpServletRequest request, HttpServletResponse response, ForwardConfig forward)
    throws IOException, ServletException
  {
    if (forward == null) {
      return;
    }

    if (log.isDebugEnabled()) {
      log.debug("processForwardConfig(" + forward.getPath() + ", " + forward.getContextRelative() + ")");
    }

    if (processTilesDefinition(forward.getPath(), forward.getContextRelative(), request, response))
    {
      if (log.isDebugEnabled()) {
        log.debug("  '" + forward.getPath() + "' - processed as definition");
      }

      return;
    }

    if (log.isDebugEnabled()) {
      log.debug("  '" + forward.getPath() + "' - processed as uri");
    }

    String path = forward.getPath();

    int period = path.lastIndexOf(".");
    if ((period >= 0) && 
      (path.substring(period).startsWith(".do"))) {
      int slash = path.lastIndexOf("/");
      if ((period >= 0) && (period > slash)) {
        path = path.substring(0, period);
      }

      ActionMapping mapping = (ActionMapping)this.moduleConfig.findActionConfig(path);
    }

    super.processForwardConfig(request, response, forward);
  }

  protected Action processActionCreate(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping)
    throws IOException
  {
    try
    {
      return getDelegateAction(mapping);
    } catch (NoSuchBeanDefinitionException ex) {
      if (mapping.getType() != null) {
        return super.processActionCreate(request, response, mapping);
      }
    }
    return null;
  }

  protected boolean processForm(HttpServletRequest request, HttpServletResponse response, ActionForm form, ActionMapping mapping, Action action)
    throws IOException, ServletException
  {
    if (!(form instanceof PojoForm)) {
      processPopulate(request, response, form, mapping);
      return processValidate(request, response, form, mapping);
    }

    ActionMessages errors = new ActionErrors();
    errors.add(populatePojoForm(request, form, mapping));
    errors.add(validatePojoForm(request, response, form, mapping));

    if ((action instanceof ValidatingAction)) {
      errors.add(validateAction(request, response, form, mapping, (ValidatingAction)action));
    }

    if (errors.isEmpty()) {
      if (log.isTraceEnabled())
        log.trace("  No errors detected, accepting input");
    }
    else {
      handleActionErrors(request, response, form, mapping, errors);
      return false;
    }

    return true;
  }

  protected ActionErrors populatePojoForm(HttpServletRequest request, ActionForm form, ActionMapping mapping)
  {
    if (!(form instanceof PojoForm)) {
      throw new IllegalArgumentException(form + " must be a PojoForm");
    }
    if ((request.getParameter("org.apache.struts.taglib.html.CANCEL") != null) || (request.getParameter("org.apache.struts.taglib.html.CANCEL.x") != null))
    {
      request.setAttribute("org.apache.struts.action.CANCEL", Boolean.TRUE);

      return null;
    }

    String multipart = mapping.getMultipartClass();
    if (multipart != null) {
      request.setAttribute("org.apache.struts.action.mapping.multipartclass", multipart);
    }
    form.setServlet(this.servlet);
    form.reset(mapping, request);

    return ((PojoForm)form).populate(request);
  }

  protected ActionMessages validatePojoForm(HttpServletRequest request, HttpServletResponse response, ActionForm form, ActionMapping mapping)
  {
    if ((form == null) || (!mapping.getValidate())) return null;

    if (request.getAttribute("org.apache.struts.action.CANCEL") != null) {
      if (log.isDebugEnabled())
        log.debug(" Cancelled transaction, skipping validation");
      return null;
    }

    return form.validate(mapping, request);
  }

  protected ActionMessages validateAction(HttpServletRequest request, HttpServletResponse response, ActionForm form, ActionMapping mapping, ValidatingAction action)
  {
    if ((form == null) || (!mapping.getValidate())) return null;

    if (request.getAttribute("org.apache.struts.action.CANCEL") != null) {
      if (log.isDebugEnabled())
        log.debug(" Cancelled transaction, skipping validation");
      return null;
    }

    return action.validate(mapping, form, request);
  }

  protected void handleActionErrors(HttpServletRequest request, HttpServletResponse response, ActionForm form, ActionMapping mapping, ActionMessages errors)
    throws IOException, ServletException
  {
    MultipartRequestHandler handler = form.getMultipartRequestHandler();
    if (handler != null) {
      if (log.isTraceEnabled()) log.trace("  Rolling back multipart request");
      handler.rollback();
    }

    String input = mapping.getInput();
    if (input == null) {
      if (log.isTraceEnabled()) log.trace("  Validation failed but no input form available");
      response.sendError(500, getInternal().getMessage("noInput", mapping.getPath()));

      return;
    }

    if (log.isDebugEnabled()) log.debug(" Validation failed, returning to '" + input + "'");

    request.setAttribute("org.apache.struts.action.ERROR", errors);

    ActionForward forward = mapping.findForward(input);
    if (this.moduleConfig.getControllerConfig().getInputForward())
      processForwardConfig(request, response, forward);
    else
      internalModuleRelativeForward(input, request, response);
  }

  protected Object invokeMethod(Object target, String methodName, Object[] args)
  {
    Method method = null;

    if (!StringUtils.isEmptyString(methodName)) {
      method = MethodUtils.getMatchingAccessibleMethod(target.getClass(), methodName, getClasses(args));
    }

    if (method == null) {
      if (log.isDebugEnabled())
        log.debug("Unable to find method named " + methodName);
      return null;
    }

    if (log.isTraceEnabled()) {
      log.trace("Attempting to invoke method " + methodName);
    }
    Exception ex = null;
    String msg = null;
    Object returnValue = null;
    try {
      returnValue = method.invoke(target, args);
    }
    catch (IllegalAccessException e) {
      ex = e; msg = "Unable to access ";
    } catch (IllegalArgumentException e) {
      ex = e; msg = "Illegal argument passed to ";
    } catch (InvocationTargetException e) {
      ex = e; msg = "Exception occurred in ";
    }
    if (log.isDebugEnabled()) log.debug(msg + method, ex);

    return returnValue;
  }

  Class[] getClasses(Object[] objects)
  {
    if (objects == null) {
      return null;
    }
    Class[] classes = new Class[objects.length];
    for (int i = 0; i < objects.length; i++) {
      classes[i] = objects[i].getClass();
    }

    return classes;
  }

  public boolean processRoles(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping)
    throws IOException, ServletException
  {
    boolean isUserLoggedIn = false;

    String[] roles = mapping.getRoleNames();
    if ((roles == null) || (roles.length < 1)) {
      return true;
    }

    ActionForward loginForward = determineLoginForward(mapping.getPath());
    if (forwardToRequestedDestination(loginForward))
    {
      String forwardPath = mapping.getPath() + ".do";

      if (request.getQueryString() != null) {
        forwardPath = forwardPath + "?" + request.getQueryString();
      }

      saveDestination(request, new ActionForward(forwardPath, true));
    }

    processForwardConfig(request, response, loginForward);

    return false;
  }

  private boolean forwardToRequestedDestination(ActionForward loginForward)
  {
    return !loginForward.getPath().contains("checkout");
  }

  private ActionForward determineLoginForward(String requestedPath)
  {
    requestedPath = requestedPath.replaceFirst("/", "");
    String[] pathParts = requestedPath.split("/");
    String prefix = pathParts[0];
    ActionForward loginForward = new ActionForward();
    if ("checkout".equals(prefix))
      loginForward.setPath("/checkout.do?method=start");
    else if (("account".equals(prefix)) || ("wishlist".equals(prefix)))
      loginForward.setPath("/account.do?method=start");
    else {
      loginForward.setPath("/" + prefix + "/login.do");
    }
    loginForward.setRedirect(true);

    return loginForward;
  }

  private void saveDestination(HttpServletRequest request, ActionForward forward) {
      
  }
}